package com.lam.common.utils;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.lam.common.utils.file.FileUtil;

/**
 * 压缩或解压zip：
 * 由于直接使用java.util.zip工具包下的类，会出现中文乱码问题，所以使用ant.jar中的org.apache.tools.zip下的工具类
 *
 * @author lam
 */
public class ZipUtils {
	
    private static final Logger logger = LoggerFactory.getLogger(ZipUtils.class);

    private static final String CHARSET = "utf-8";
    
    private static final String ZIP_SUFFIX = ".zip";
    
    private ZipUtils(){
		throw new IllegalStateException("Utility class");
	}

    /**
     * 压缩文件或路径
     *
     * @param os 压缩的目的输出流
     * @param srcFilePaths 压缩的源文件
     * @param relativePath 在zip中的相对路径
     */
    public static boolean zipFile(OutputStream os, List<String> srcFilePaths, String relativePath) {
    	try (ZipOutputStream zipOut = new ZipOutputStream(os, Charset.forName(CHARSET));){
	        for (String filePath : srcFilePaths) {
	            handlerFile(zipOut, new File(filePath), relativePath);
	        }
	        return true;
	    } catch (IOException e) {
	    	logger.error(e.getMessage(), e);
	    } 
        return false;
    }
    
    /**
     * 压缩文件或路径
     *
     * @param targetZipPath 压缩的目的地址
     * @param srcFilePaths 压缩的源文件
     * @param relativePath 在zip中的相对路径
     */
    public static boolean zipFile(String targetZipPath, List<String> srcFilePaths, String relativePath) {
        if(StringUtils.isBlank(targetZipPath)) {
            logger.info("target zip path[{}] is null!", targetZipPath);
            return false;
        }
        if(!targetZipPath.toLowerCase().endsWith(ZIP_SUFFIX)) {
        	logger.info("target file[{}] is not .zip type file", targetZipPath);
        	return false;
        }
        try {
			return zipFile(new FileOutputStream(new File(targetZipPath)), srcFilePaths, relativePath);
		} catch (Exception e) {
			logger.error(e.getMessage(), e);
		}
        return false;
    }

    /**
     * @param zip     压缩的目的地址
     * @param zipOut
     * @param srcFile 被压缩的文件信息
     * @param path    在zip中的相对路径
     * @return 
     * @throws IOException
     */
    private static boolean handlerFile(ZipOutputStream zipOut, File srcFile, String path) throws IOException {
    	
    	logger.info("start compress file [{}] start ...", srcFile.getName());
    	
        if(!srcFile.exists()){
            logger.error("文件不存在，srcFile:{}", srcFile);
            return false;
        }
        if (StringUtils.isNotBlank(path) && !path.endsWith(File.separator)) {
            path += File.separator;
        }
        if (srcFile.isDirectory()) {
            File[] files = srcFile.listFiles();
            if (files.length == 0) {
                zipOut.putNextEntry(new ZipEntry(path + srcFile.getName() + File.separator));
                zipOut.closeEntry();
            } else {
                for (File file : files) {
                    handlerFile(zipOut, file, path + srcFile.getName());
                }
            }
            return true;
        } 
        zipSingleFile(zipOut, new FileInputStream(srcFile), srcFile.getName(), path);
        
        logger.info("compress file [{}] end!", srcFile.getName());
        return true;
    }
    
    /**
     * 压缩文件或路径
     *
     * @param os 压缩的目的输出流
     * @param relativePathFileMap 压缩的源文件流, key:在zip中的相对路径, value:压缩的源文件流(Map, key:文件名称, value:文件流数据)
     * @return
     */
    public static boolean zipFile(OutputStream os, Map<String, Map<String, InputStream>> relativePathFileMap) {
    	try (ZipOutputStream zipOut = new ZipOutputStream(os, Charset.forName(CHARSET));){
	        for (Entry<String, Map<String, InputStream>> entry : relativePathFileMap.entrySet()) {
	        	for (Entry<String, InputStream> et : entry.getValue().entrySet()) {
		        	zipSingleFile(zipOut, et.getValue(), et.getKey(), entry.getKey()+"/");
		        }
	        }
	        return true;
	    } catch (IOException e) {
	    	logger.error(e.getMessage(), e);
	    } 
        return false;
    }
    
    /**
     * 压缩文件或路径
     *
     * @param os 压缩的目的输出流
     * @param srcFileMap 压缩的源文件流, key:文件名称, value:文件流数据
     * @param relativePath 在zip中的相对路径
     * @return
     */
    public static boolean zipFile(OutputStream os, Map<String, InputStream> srcFileMap, String relativePath) {
    	try (ZipOutputStream zipOut = new ZipOutputStream(os, Charset.forName(CHARSET));){
	        for (Entry<String, InputStream> entry : srcFileMap.entrySet()) {
	        	zipSingleFile(zipOut, entry.getValue(), entry.getKey(), relativePath);
	        }
	        return true;
	    } catch (IOException e) {
	    	logger.error(e.getMessage(), e);
	    } 
        return false;
    }
    
    public static void zipSingleFile(ZipOutputStream zipOut, InputStream in, String srcFileName, String path) throws IOException{
    	try{
            zipOut.putNextEntry(new ZipEntry(path + srcFileName));
            IOUtils.copy(in, zipOut);
        } catch (IOException e) {
            logger.error("compress file [{}] IOException"+srcFileName, e);
            throw e;
        } finally {
        	if(zipOut != null){
        		try {
					zipOut.closeEntry();
				} catch (Exception e) {
					logger.error(e.getMessage(), e);
				}
        	}
		}
    }
    
    

    /**
     * 解压缩ZIP文件，将ZIP文件里的内容解压到targetDIR目录下
     *
     * @param zipPath 待解压缩的ZIP文件名
     * @param descDir 目标目录
     */
    public static List<File> upzipFile(String zipPath, String descDir) {
        return upzipFile(new File(zipPath), descDir);
    }

    /**
     * 对.zip文件进行解压缩
     *
     * @param zipFile 解压缩文件
     * @param descDir 压缩的目标地址，如：D:\\测试 或 /mnt/d/测试
     * @return
     */
    public static List<File> upzipFile(File zipFile, String descDir) {
        List<File> listTmp = new ArrayList<>();
        try (ZipFile zipFileTmp = new ZipFile(zipFile, Charset.forName("GBK"))){
            for (Enumeration<?> entries = zipFileTmp.entries(); entries.hasMoreElements(); ) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                File fileTmp = new File(descDir + File.separator + entry.getName());
                if (entry.isDirectory()) {
                    fileTmp.mkdirs();
                } else {
                    if(copyFile(zipFileTmp, entry, fileTmp)){
                    	listTmp.add(fileTmp);
                    }
                }
            }
        } catch (IOException e) {
        	logger.error(e.getMessage(), e);
        }
        return listTmp;
    }

	private static boolean copyFile(ZipFile zipFileTmp, ZipEntry entry, File fileTmp) {
		File parentTmp = fileTmp.getParentFile();
        if (!parentTmp.exists()) {
            parentTmp.mkdirs();
        }
		try (FileOutputStream out = new FileOutputStream(fileTmp); 
				InputStream in = zipFileTmp.getInputStream(entry)){
			IOUtils.copy(in, out);
			return true;
		} catch (IOException e) {
			logger.error(e.getMessage(), e);
		}
		return false;
	}

    /**
     * 对临时生成的文件夹和文件夹下的文件进行删除
     */
    public static void deletefile(String delpath) {
        File file = new File(delpath);
        if (!file.isDirectory()) {
        	FileUtil.deleteFile(file);
        } else if (file.isDirectory()) {
            String[] filelist = file.list();
            for (int i = 0; i < filelist.length; i++) {
                File delfile = new File(delpath + File.separator + filelist[i]);
                if (!delfile.isDirectory()) {
                	FileUtil.deleteFile(delfile);
                } else if (delfile.isDirectory()) {
                    deletefile(delpath + File.separator + filelist[i]);
                }
            }
            FileUtil.deleteFile(file);
        }
    }

    public static void main(String[] args) {

        List<String> list = new ArrayList<>();
        list.add("D:\\download\\2019-07-25");
        list.add("D:\\download\\补发20190731短信发送客户名单-代扣平台.xlsx");
        zipFile("D:/aaaaa.zip", list,"file");

    }

}